/* vim:ft=objc:
 * $Id: ATOM.m 145 2005-05-24 21:52:41Z bmoore $
 *
 * Copyright (c) 2004, 2005  Branden J. Moore.
 *
 * This file is part of MacBiff, and is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * MacBiff is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MacBiff; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307 USA.
 *
 */

#import <AppKit/NSNibLoading.h>
#import "ATOM.h"

#import <MacBiffPlugIn/debug.h>
#import <MacBiffPlugIn/xmalloc.h>
#import <MacBiffPlugIn/PreferencesController.h>
#import <MacBiffPlugIn/Keychain.h>
#import <MacBiffPlugIn/Folder.h>
#import <MacBiffPlugIn/Message.h>

#include <errno.h>
#include <stdlib.h>

@interface NSString (Insensitive)
- (BOOL) isSame: (NSString*) str;
@end

@implementation NSString (Insensitive)
- (BOOL) isSame: (NSString*) str
{
	return ([self caseInsensitiveCompare: str] == NSOrderedSame);
}
@end



@implementation ATOM

+ (NSString *) plugInName
{
	return @"RSS (ATOM)";
}


+ (NSString *) plugInVersion
{
	return @"1.0.0";
}


+ (NSString *) plugInAuthor
{
	return @"Branden J. Moore <bmoore@forkit.org>";
}


+ (NSString *) plugInUrl
{
	return @"http://www.forkit.org/macbiff/";
}


- (void) statusAt: (double) pos withText: (NSString*) txt
{
	if ( actControl ) {
		[actControl updateStatus: pos withText: txt];
	}
}


- (void) statusBy: (double) pos withText: (NSString*) txt
{
	if ( actControl ) {
		[actControl incrementStatus: pos withText: txt];
	}
}


/* Read in the preferences, or set up the defaults */
- (void) initializePrefs
{
	if ( ![mPrefs objectForKey: @"URL"] ) {
		/* No preferences set.  Set defaults */
		[mPrefs setObject: @"" forKey: @"URL"];
		[mPrefs setObject: [NSNumber numberWithBool: NO] forKey: @"Use Authentication"];
		[mPrefs setObject: @"" forKey: @"Username"];
		[mPrefs setObject: [NSNumber numberWithBool: YES] forKey: @"Use Default Actions"];
	}

	NSURL *url = [NSURL URLWithString: [mPrefs objectForKey: @"URL"]];
	NSString *tpw = [keychain getPasswordForUser:
			[mPrefs objectForKey: @"Username"]
		onHost: [url host]
		atPort: [[url port] intValue]
		withProto: ([[url scheme] isEqualToString: @"https"]) ?
			kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP
		byAsking: NO];
	passwd = [tpw retain];
	passwd = Nil;

	folder = [[Folder alloc] initWithName: @"" andFullPath: @""];
	[folders addObject: folder];
}


- (id) initWithName: (NSString*) n
{
	if ( ( self = [super initWithName: n] ) != Nil ) {
		dprintf("Initializing ATOM...\n");
		if ( ![NSBundle loadNibNamed: @"atom" owner: self] ) {
			alert("Unable to Load ATOM Nib!\n");
		}
		mPrefs = [prefs account: n];
		folders = [[NSMutableArray alloc] init];
		[self initializePrefs];
	}
	return self;
}


- (void) dealloc
{
	if ( folder ) {
		[folder release];
		folder = Nil;
	}
	[super dealloc];
}


- (void) setupView
{
	BOOL use_auth = [[mPrefs objectForKey: @"Use Authentication"] boolValue];
	[urlTextField setStringValue:
		[mPrefs objectForKey: @"URL"]];
	[useAuthButton setState: use_auth ? NSOnState : NSOffState];
	[loginNameTextField setStringValue:
		[mPrefs objectForKey: @"Username"]];
	[loginNameTextField setEnabled: use_auth];
	[passwordTextField setStringValue: passwd ? passwd : @""];
	[passwordTextField setEnabled: use_auth];
}

- (NSURL*) getURL
{
	NSMutableString *urlStr = [[[NSMutableString alloc] init] autorelease];
	[urlStr setString: [mPrefs objectForKey: @"URL"]];

	if ( [[mPrefs objectForKey: @"Use Authentication"] boolValue] ) {
		/* Gotta add username & Password */
		NSRange range = [urlStr rangeOfString: @"//"];
		if ( range.location != NSNotFound ) {
			NSString *user = (NSString*)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)[mPrefs objectForKey: @"Username"], NULL, CFSTR("@:"), kCFStringEncodingUTF8);
			dprintf("Adding '%s' as a username\n", [user cString]);
			NSString *pass = (NSString*)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)passwd, NULL, CFSTR("@:"), kCFStringEncodingUTF8);
			NSString *auth = [NSString stringWithFormat:
				@"%@:%@@", user, pass];
			[urlStr insertString: auth
				atIndex: range.location+range.length];
		}
	}

	return [NSURL URLWithString: urlStr];
}
- (int) checkMail
{
	int retval = 0;
	NSAutoreleasePool *mPool = [[NSAutoreleasePool alloc] init];
	BOOL normal_end = YES;
	NSXMLParser *parser = Nil;

	in_author = NO;
	stop_now = 0;
	newNew = newTotal = newUnread = 0;
	dprintf("Account '%s' to check feed.\n", [acctName cString]);
	/* Verify if we have a password */

	if ( !passwd  && [[mPrefs objectForKey: @"Use Authentication"] boolValue]) {
		NSURL *url = [NSURL URLWithString: [mPrefs objectForKey: @"URL"]];
		NSString *tpw = [keychain getPasswordForUser:
				[mPrefs objectForKey: @"Username"]
			onHost: [url host]
			atPort: [[url port] intValue]
			withProto: ([[url scheme] isEqualToString: @"https"]) ?
				kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP
			byAsking: YES];
		passwd = [tpw retain];
	}


	@try {
	/* Download FEED */
		[self statusAt: 2.0 withText: @"Downloading Feed..."];
		dprintf("Begin Feed Download.\n");
		NSURL *url = [self getURL];
		NSError *err = Nil;
		NSString *str = [[[NSString alloc] initWithContentsOfURL: url encoding: NSUTF8StringEncoding error: &err] autorelease];
		dprintf("Result:  '%s'\n", [str cString]);
		if ( err && [err domain] != Nil ) {
			alert("URL errorDomain: '%s' with code: %d.\n",
				[[err domain] cString], [err code]);
		}
		NSData *data = [str dataUsingEncoding: NSUTF8StringEncoding];
		parser = [[[NSXMLParser alloc] initWithData: data]
			autorelease];
		if ( parser == Nil ) {
			alert("Unable to get parser\n");
		}
		[parser setDelegate: self];

	/* Parse FEED */
		oldMsgList = [[folder messageList] copy];
		[folder resetMessageList];
		dprintf("Parse Feed.\n");
		[self statusAt: 15.0 withText: @"Parsing Feed..."];
		[parser parse];
		newUnread = [folder unreadMail];
		newTotal = [folder totalMail];
		newNew = [folder newMail];
		if ( stop_now ) {
			normal_end = NO;
		}

	/* Log Out */
		[self statusAt: 95.0 withText: @"Finishing Up."];
	}
	@catch ( NSException *excp ) {
		NSString *exn = [excp reason];
		alert("Exception was thrown\n");
		alert("Thrown: '%s'\n", [exn cString]);
		normal_end = NO;
		@throw;
	}
	@finally {
		dprintf("Finished.\n");
	}

	if ( normal_end ) {
		total = newTotal;
		new = newNew;
		unread = newUnread;
	}

	if ( actControl )
		[actControl completed];

	[mPool release];
	stop_now = 0;
	return retval;
}


- (void) stopNow: (id) obj
{
	stop_now = 1;
}


- (unsigned int) total
{
	return total;
}


- (unsigned int) unread
{
	return unread;
}


- (unsigned int) new
{
	return new;
}


- (IBAction)setLoginName:(id)sender
{
	[mPrefs setObject: [loginNameTextField stringValue]
		forKey: @"Username"];
	[prefs commitAccounts];
}


- (IBAction)setPassword:(id)sender
{
	if ( passwd ) [passwd release];
	passwd = [[NSString alloc] initWithString:
				[passwordTextField stringValue]];
	NSURL *url = [NSURL URLWithString: [mPrefs objectForKey: @"URL"]];

	[keychain savePassword: passwd
		forUser: [mPrefs objectForKey: @"Username"]
		onHost: [url host]
		atPort: [[url port] intValue]
		withProto:  ([[url scheme] isEqualToString: @"https"]) ?
			kSecProtocolTypeHTTPS : kSecProtocolTypeHTTP];
}


- (IBAction)setURL:(id)sender
{
	[mPrefs setObject: [urlTextField stringValue]
		forKey: @"URL"];
	[prefs commitAccounts];
}


- (IBAction)setUseAuth:(id)sender
{
	BOOL use_auth = ([useAuthButton state] == NSOnState);
	[mPrefs setObject: [NSNumber numberWithBool: use_auth]
		forKey: @"Use Authentication"];
	[prefs commitAccounts];
	[loginNameTextField setEnabled: use_auth];
	[passwordTextField setEnabled: use_auth];
}

/* XMLParser Delegate Methods */

- (void) parser: (NSXMLParser*) parser parseErrorOccurred: (NSError*) pE
{
	if ( !stop_now ) {
		alert("Parse error: '%s' with code: %d.\n",
				[[pE domain] cString], [pE code]);
		alert("Line: %d  Column: %d\n", [parser lineNumber],
				[parser columnNumber]);
	}
}


- (void) parserDidStartDocument: (NSXMLParser*) parser
{
	dprintf("Beginning Document\n");
}


- (void) parserDidEndDocument: (NSXMLParser*) parser
{
	dprintf("Ending Document\n");
}


- (void) parser: (NSXMLParser*) parser
didStartElement: (NSString*) elementName
   namespaceURI: (NSString*) namespaceURI
  qualifiedName: (NSString*) qualifiedName
     attributes: (NSDictionary*) attributeDict
{
	if ( stop_now ) [parser abortParsing];
	dprintf("Starting element: '%s'\n", [elementName cString]);
	if ( [elementName isSame: @"entry"] ) {
		if ( currEntry ) {
			alert("Malformed ATOM feed.  Dropping previous entry\n");
			[currEntry release];
		}
		currEntry = [[NSMutableDictionary alloc] init];
	} else if ( [elementName isSame: @"author"] ) {
		if ( currAuthor ) {
			[currAuthor release];
		}
		currAuthor = [[NSMutableDictionary alloc] init];
		in_author = YES;
	} else if ( [elementName isSame: @"link"] && currEntry ) {
		NSString *url = [attributeDict objectForKey: @"href"];
		if ( !url ) url = [attributeDict objectForKey: @"HREF"];
		if ( url ) {
			dprintf("Setting Entry URL: %s\n", [url cString]);
			[currEntry setObject: url forKey: @"URL"];
		}
	}
	if ( currString ) {
		[currString release];
		currString = Nil;
	}

}


- (void) parser: (NSXMLParser*) parser
  didEndElement: (NSString*) elementName
   namespaceURI: (NSString*) namespaceURI
  qualifiedName: (NSString*) qName
{
	if ( stop_now ) [parser abortParsing];
	dprintf("Ending element: '%s'\n", [elementName cString]);
	if ( [elementName isSame: @"entry"] ) {
		/* Finish off the entry - Build a Message */
		NSString *from = [currAuthor objectForKey: @"Name"];
		NSString *email = [currAuthor objectForKey: @"Email"];
		NSString *subject = [currEntry objectForKey: @"Subject"];
		NSString *dateStr = [currEntry objectForKey: @"Date"];
		NSDate *date = [NSDate dateWithNaturalLanguageString: dateStr];
		NSString *uidStr = [currEntry objectForKey: @"UID"];
		unsigned uid = [uidStr hash];
		NSString *urlStr = [currEntry objectForKey: @"URL"];
		int idx;

		if ( !from && email ) from = email;
		if ( !email ) email = @"Unavailable";

		/* Need to do new checking */
		Message *msg = [Message newMessageFrom: from
						 email: email
					   withSubject: subject
						atDate: date
					       withUid: uid];

		if ( (idx = [oldMsgList indexOfObject: msg]) != NSNotFound ) {
			Message *m = [oldMsgList objectAtIndex: idx];
			[m setNew: NO];
			[folder addMessage: m];
		} else {
			if ( urlStr ) {
				[msg setURL: urlStr];
				NSMenuItem *mi = [msg menuItem];
				[mi setRepresentedObject: urlStr];
				[mi setTarget: self];
				[mi setAction: @selector(clickedItem:)];
			}
			[folder addMessage: msg];
		}
		[currEntry release]; currEntry = Nil;
	} else if ( [elementName isSame: @"author"] ) {
		in_author = NO;
	} else if ( [elementName isSame: @"email"] && currAuthor ) {
		dprintf("Setting From Email: '%s\n", [currString cString]);
		[currAuthor setObject: currString forKey: @"Email"];
	} else if ( [elementName isSame: @"name"] && currAuthor ) {
		dprintf("Setting From Name: '%s\n", [currString cString]);
		[currAuthor setObject: currString forKey: @"Name"];
	} else if ( [elementName isSame: @"title"] && currEntry ) {
		dprintf("Setting Subject: '%s\n", [currString cString]);
		[currEntry setObject: currString forKey: @"Subject"];
	} else if ( [elementName isSame: @"modified"] && currEntry ) {
		dprintf("Setting Date: '%s\n", [currString cString]);
		NSDate *date = [NSDate dateWithNaturalLanguageString: currString];
		dprintf("Setting Date: '%s\n", [[date description] cString]);
		[currEntry setObject: currString forKey: @"Date"];
	} else if ( [elementName isSame: @"id"] && currEntry ) {
		dprintf("Setting UID: '%s\n", [currString cString]);
		dprintf("Setting UID: '%u\n", [currString hash]);
		[currEntry setObject: currString forKey: @"UID"];
	}
}


- (void) parser: (NSXMLParser*) parser foundCharacters: (NSString*) str
{
	if ( stop_now ) [parser abortParsing];
	dprintf("Found '%s'\n", [str cString]);
	if ( !currString )
		currString = [[NSMutableString alloc] init];

	[currString appendString: str];
}


- (void) clickedItem: (NSMenuItem*) mi
{
	NSString *urlStr = [mi representedObject];
	[[NSWorkspace sharedWorkspace]
		openURL: [NSURL URLWithString: urlStr]];
}

@end
